package current.interrupt;

import lombok.extern.log4j.Log4j2;
import org.junit.Test;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author dycong
 * @date 2018/10/18 13:36
 */
@Log4j2
public class InterruptTest2 {

    /*todo 如果线程在 RUNNABLE  中，且没有执行IO操作，interrupt()只是会设置线程的中断标志位，没有任何其它作用。
    todo 线程应该在运行过程中合适的位置检查中断标志位，比如说，如果主体代码是一个循环，可以在循环开始处进行检查*/
    public static class ThreadRunnable extends Thread {
        public void run() {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Someone interrupted me.");
                } else {
                    System.out.println("Thread is Going...");
                }
            }
        }
    }

    @Test
    public void testThreadRunnable() throws InterruptedException {
        ThreadRunnable t = new ThreadRunnable();
        t.start();
        Thread.sleep(3000);
        t.interrupt();
    }

    /*todo WAITING/TIMED_WAITING
    线程执行如下方法会进入WAITING状态：
    public final void join() throws InterruptedException
    public final void wait() throws InterruptedException

    todo 执行如下方法会进入TIMED_WAITING状态：
    public final native void wait(long timeout) throws InterruptedException;
    public static native void sleep(long millis) throws InterruptedException;
    public final synchronized void join(long millis) throws InterruptedException
    todo 在这些状态时，对线程对象调用interrupt()会使得该线程抛出InterruptedException，需要注意的是，抛出异常后，中断标志位会被清空，而不是被设置。
    todo InterruptedException是一个受检异常，线程必须进行处理。我们在异常处理中介绍过，处理异常的基本思路是，如果你知道怎么处理，就进行处理，如果不知道，就应该向上传递，通常情况下，你不应该做的是，捕获异常然后忽略。
    */

    @Test
    public void testThreadWaiting() {
        Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    log.error("sdf", e);
                    /*抛出异常后，中断标志位会被清空，而不是被设置*/
                    System.out.println(isInterrupted());
                    /*有些情况，不能向上传递异常，比如Thread的run方法，它的声明是固定的，不能抛出任何受检异常，这时，应该捕获异常，进行合适的清理操作，清理后，一般应该调用Thread的interrupt方法设置中断标志位，使得其他代码有办法知道它发生了中断。*/
                    Thread.currentThread().interrupt();
                }
            }
        };
        t.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            log.error("", e);
        }
        t.interrupt();
    }

    /**
     * todo  BLOCKED
     * todo 如果线程在等待锁，对线程对象调用interrupt()只是会设置线程的中断标志位，线程依然会处于BLOCKED状态，也就是说，interrupt()并不能使一个在等待锁的线程真正”中断”
     */
    private static Object lock = new Object();

    private class A extends Thread {
        @Override
        public void run() {
            synchronized (lock) {
                while (!Thread.currentThread().isInterrupted()) {
                }
            }
            System.out.println("exit");
        }
    }

    @Test
    public void testThreadBlocked() throws InterruptedException {
        synchronized (lock) {
            A a = new A();
            a.start();
            Thread.sleep(1000);
/*test方法在持有锁lock的情况下启动线程a，而线程a也去尝试获得锁lock，所以会进入锁等待队列，随后test调用线程a的interrupt方法并等待线程线程a结束，
线程a会结束吗？不会，interrupt方法只会设置线程的中断标志，而并不会使它从锁等待队列中出来。*/
            a.interrupt();
            /*todo join() 主线程等待 a线程执行完再回到主线程，此时a线程又是 BLOCKED .证明interrupt()并不能使一个在等待锁的线程真正”中断”*/
            a.join();
        }
    }

    @Test
    public void testThreadBlocked2() throws InterruptedException {
        synchronized (lock) {
            A a = new A();
            a.start();
            Thread.sleep(1000);
/*test方法在持有锁lock的情况下启动线程a，而线程a也去尝试获得锁lock，所以会进入锁等待队列，随后test调用线程a的interrupt方法并等待线程线程a结束，
线程a会结束吗？不会，interrupt方法只会设置线程的中断标志，而并不会使它从锁等待队列中出来。*/
            a.interrupt();
            /*todo 去掉 join() 主线程等待 主线程执行完释放锁，a线程得到锁，由于 a.interrupt() 置位，所以任务结束，释放线程*/
//            a.join();
        }
    }

    /**
     * todo  NEW/TERMINATE
     * todo 如果线程尚未启动(NEW)，或者已经结束(TERMINATED)，则调用interrupt()对它没有任何效果，中断标志位也不会被设置。
     */

    private class AB extends Thread {
        @Override
        public void run() {
        }
    }

    @Test
    public void test() throws InterruptedException {
        AB a = new AB();
        // TODO: 2018/10/18 NEW
        a.interrupt();
        System.out.println(a.isInterrupted());
        a.start();
        Thread.sleep(100);
        // TODO: 2018/10/18 TERMINATE
        a.interrupt();
        System.out.println(a.isInterrupted());
    }

    /**
     * IO操作
     * 如果线程在等待IO操作，尤其是网络IO，则会有一些特殊的处理，我们没有介绍过网络，这里只是简单介绍下。
     * 1. 如果IO通道是可中断的，即实现了InterruptibleChannel接口，则线程的中断标志位会被设置，同时，线程会收到异常ClosedByInterruptException。
     * 2. 如果线程阻塞于Selector调用，则线程的中断标志位会被设置，同时，阻塞的调用会立即返回。
     * 3. 我们重点介绍另一种情况，InputStream的read调用，该操作是不可中断的，如果流中没有数据，read会阻塞 (但线程状态依然是RUNNABLE)，且不响应interrupt()，与synchronized类似，调用interrupt()只会设置线程的中断标志，而不会真正”中断”它，我们看段代码。
     */
    private class ABC extends Thread {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    /*线程t启动后调用System.in.read()从标准输入读入一个字符，不要输入任何字符，我们会看到，调用interrupt()不会中断read()，线程会一直运行。*/
                    System.out.println(System.in.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("exit");
        }
    }

    @Test
    public void testIO() throws InterruptedException {
        ABC t = new ABC();
        t.start();
        Thread.sleep(10000);
        t.interrupt();
    }

    // TODO: 2018/10/18 不过，有一个办法可以中断read()调用，那就是调用流的close方法，我们将代码改为：
    private static class ABCD extends Thread {
//        BufferedInputStream inputStream = (BufferedInputStream) System.in;
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println(System.in.read());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("exit");
        }

        public void cancel() {
            try {
                System.in.close();
                System.out.println("qqq");
                //                System.in.reset();
            } catch (IOException e) {
            }
            interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ABCD t = new ABCD();
        t.start();

        Thread.sleep(100);
        t.cancel();

    }

    /**
     * todo 对于以线程提供服务的程序模块而言，它应该封装取消/关闭操作，提供单独的取消/关闭方法给调用者，类似于InterruptReadDemo中演示的cancel方法，
     * todo 外部调用者应该调用这些方法而不是直接调用interrupt。

     todo Java并发库的一些代码就提供了单独的取消/关闭方法，比如说，Future接口提供了如下方法以取消任务：
     boolean cancel(boolean mayInterruptIfRunning);

     todo 再比如，ExecutorService提供了如下两个关闭方法：
     void shutdown();
     List shutdownNow();

     Future和ExecutorService的API文档对这些方法都进行了详细说明，这是我们应该学习的方式。关于这两个接口，我们后续章节介绍。*/
}


